package com.cctc.juc.source.Bitc.Dsynchronized;
/**
 * 【synchronized 关键字】
 * 1. 简述：synchronized 是 Java 虚拟机提供的一个关键字，主要用于解决多线程环境下共享变量的线程同步问题，其可以保证被其修饰的方法或代码块在任意时刻只有一个线程（互斥）执行。
 * 2. 从语法上讲，synchronized 关键字主要有三种用法：
 * 1）同步普通（对象）方法；
 * 2）同步静态（类）方法；
 * 3）[推荐] 同步（临界区）普通（非静态）代码块 --> synchronized 同步的范围越小越好，保证其他代码可以异步执行，提高运行效率。
 * 提示：synchronized 不能（或不需要）用于类级别的（静态）代码块。因为类级别的（静态）代码块在加载顺序上要优于任何方法的，其执行顺序只和代码位置先后有关，不会出现争抢现象，自然不需要同步。
 * 3. synchronized 关键字的作用主要有三个：
 * 1）原子性，确保所有线程互斥地访问临界区代码块，底层是通过 Java 内置锁（Monitor 监视器）来保证的；
 * 2）内存可见性，确保其中一个线程对临界区资源的修改能够立即被其他线程可见；
 * 底层是通过 JMM [内存读写（load | store）屏障] 进行保证的：
 * - 在对一个（共享）变量（临界区资源） unlock 之前，必须要（store）同步到主（堆）内存中；
 * - 在对一个（共享）变量（临界区资源） lock 之前，必须清空工作（栈）内存中缓存的值，然后重新从主（堆）内存中（load）加载并进行初始化赋值。
 * 3）指令有序性，禁止对临界区代码块进行重排序；
 * 底层是通过 JMM Happens-Before 先行发生规则中的 {管理锁定规则} 来保证的：
 * - 管理锁定规则：对于同一个锁来说，前一个线程的解锁操作一定要先行发生于后一个线程的加锁操作。
 * 总的来说，synchronized 是 JVM 为我们提供的一把独占的、可重入（避免死锁发生）的、非公平的隐式同步锁，抢锁过程是阻塞式的、不可中断式的。
 * 当同步方法执行结束或执行期间抛出异常，JVM 会自动释放锁，不需要外部干预。
 * --------------------------------------------------------------------------------------
 * 提示：下面是 synchronized 关键字的底层实现原理，可以尽情发挥。
 * --------------------------------------------------------------------------------------
 * 4. synchronized 关键字的底层原理：
 * 每个 Java 对象内部都隐含有一把 [内置锁（对象锁、隐式锁）]，使用 synchronized 关键字修饰的同步方法或同步代码块本质上就是使用 [Java 内置锁] 对临界区代码块进行排他性（互斥）保护，以达到原子操作的目的。
 * 在 JDK 1.6 版本之前，所有的 [Java 内置锁] 都是 “重量级锁”，该锁是 JVM 通过调用操作系统的 Mutex 互斥原语来实现的，抢锁失败的（用户）线程会被挂起、等待被操作系统重新调度，会导致 CPU 在用户态和核心态之间来回切换，开销太大。
 * 因此，为了降低 “重量级锁” 带来的性能消耗，JDK 1.6 引入了 “偏向锁” 和 “轻量级锁” 的实现。至此，[Java 内置锁] 一共存在 4 种状态：无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。这些状态随着锁竞争情况逐渐升级，且只升不降，以提高 [Java 内置锁] 的使用效率。
 * 【偏向锁（乐观锁）】、【轻量级锁（乐观锁）】 的底层实现原理：基于 CAS 自旋操作实现的乐观（无，版本控制）锁。
 * 【重量级锁（悲观锁）】 的底层实现原理：通过使用 javap -c 命令反编译使用 synchronized 修饰的代码块，我们发现，在 synchronized 同步代码块的字节码前后会多出两个 monitorenter、monitorexit 指令。关于这两条指令，JVM 规范中的描述如下：
 * 每个（Object [instanceOopDesc] 实例或 Class [instanceKlass] 实例）对象内部都有一个监视器（monitor），该 monitor 监视器与所属对象一起创建、销毁。
 * 本质上，monitor 就是一个同步工具，相当于一个运行许可证，其义务是保证（同一时刻）只有一个线程可以访问（互斥）临界区代码块。
 * - monitorenter 指令：当 monitor 被占用时就会处于锁定状态，线程执行 monitorenter 指令时会尝试获取 monitor 的所有权，过程如下：
 * 1）如果 monitor 的进入数为 0，则该线程进入 monitor，然后将进入数设置为 1，该线程成为 monitor 的拥有者；
 * 2）如果线程已经占有该 monitor，只是重新进入（可重入），则进入数 + 1；
 * 3）如果 monitor 已经被其他线程占用，则当前线程进入阻塞状态，直到 monitor 的进入数为 0 时被唤醒，再重新尝试获取 monitor 的所有权。
 * - monitorexit 指令：首先，需要保证执行 monitorexit 指令的线程必须是指定对象内的 monitor 监制器锁的所有者。当执行 monitorexit 时，monitor 的进入数减 1，一旦进入数变为 0，那么线程将退出 monitor，不再是这个 monitor 的拥有者，其他被此 monitor 阻塞的线程将被唤醒，尝试去获取这个 monitor 的所有权。
 * 提示：对于同步方法，JVM 并没有直接使用 monitorenter、monitorexit 指令，而是通过 ACC_SYNCHRONIZED 标识符来实现线程同步的。但底层实现原理都是一致的，都是通过互斥获取及释放指定对象内的 monitor 监制器锁来完成的，只是同步方法使用的是一种隐式的标识符方式而无需通过字节码来实现的。
 * 此外，我们通常会在 synchronized 同步方法或同步代码块中（已经取得 monitor 的拥有权）结合使用 monitor 所属 Object 对象的 wait() 和 notify[All]() 方法实现（基于 monitor 监视器锁内部的 WaitSet 条件队列）线程（条件）等待与通知机制。
 * [重量级锁] 操作系统中的数据结构：
 * ObjectMonitor::ObjectMonitor() {
 * _object = NULL;      # 所属（Object [instanceOopDesc] 实例或 Class [instanceKlass] 实例）对象
 * _owner = NULL;       # 拥有者线程
 * _recursions = 0;     # 重入次数
 * cxq = NULL;          # 竞争队列（非公平），多线程竞争 [重量级锁] 时进入的单向链表
 * _EntryList = NULL;   # 阻塞队列，线程释放 [重量级锁] 拥有权时，从该双向链表中唤醒阻塞线程
 * _WaitSet = NULL;     # 条件队列，线程调用 notify[All]()时，从该（条件）双向循环链表中唤醒等待线程
 * ... ...
 * }
 * “重量级锁” 内部抢锁流程：
 * 1）首先，线程通过 CAS 自旋获取 [重量级锁]；
 * 2）如果 1）失败，线程将进入 Cxq 竞争队列；
 * 3）在 Owner 线程释放锁时，JVM 会从 Cxq 中迁移线程到 EntryList 内，并指定 Head 线程作为 OnDeck 候选线程，重新竞争 [重量级锁]；
 * 4）OnDeck 候选线程与 1）中的 CAS 自旋线程竞争 [重量级锁]，此过程显然是非公平的。
 * “重量级锁” 内部（条件）等待与唤醒流程：
 * 1）当前持锁线程调用 wait() 方法，释放 [重量级锁]，并进行条件等待，被放置到 WaitSet 条件队列；
 * 2）等到条件满足时，新持锁线程调用 notify[All]() 方法，释放 [重量级锁]，唤醒 WaitSet 条件队列中的等待线程，重新竞争 [重量级锁]。
 * 3）唤醒线程会与 CAS 自旋线程竞争 [重量级锁]，此过程显然也是非公平的。
 * “重量级锁” 性能分析：
 * “重量级锁” 是基于 pthread_mutex_lock 系统调用实现的，pthread_mutex_lock 系统调用是 Linux 内核为上层应用程序提供的互斥锁访问机制。
 * 所以 [重量级锁] 会造成 CPU 在用户态和核心态之间频繁切换，开销太大。
 * 因此，为了降低 [重量级锁] 带来的性能消耗，JDK 1.6 引入了 “偏向锁” 和 “轻量级锁” 的实现。至此，[Java 内置锁] 一共存在 4 种锁状态：无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。这些锁状态随着锁竞争情况逐渐升级，且只升不降，以提高获取锁和释放锁的效率。
 * [Java 内置锁] 的 4 种状态是通过 Java 对象头中的 Mark Word（标记字）进行控制的。
 * 下面，我们结合 Java 对象结构的内部组成以及 4 种锁状态的变化过程剖析一下 Java 内置锁的实现原理。
 * ------------------------------------------------------------------------------------------------------------
 * [Java 对象结构]
 * 说明：不同的 JVM 对象结构实现是不一样的，这里以 HotSpot VM 为例进行剖析。
 * JVM 底层原理（了解即可）：HotSpot VM 中的 Java 对象是基于 oop-klass 模型（对象 [普通对象指针] - 类）进行构建的。每当在 Java 代码中创建一个普通对象，JVM 就会创建一个 C++ 类型的 instanceOopDesc 实例，并存放在堆内存中。类似地，Java 数组对应一个 arrayOopDesc 实例。特别地，Class 类实例对应于一个 instanceKlass 实例，存放在方法区或元空间内；此外，Class 类实例在 Java 层面也是一个普通对象，同样也会为其创建一个 instanceOopDesc 实例，存放在堆内存中，该实例作为 instanceKlass 实例的 “镜像”，被其所指向。
 * 在了解了 oop-klass 模型之后，我们来看一下 [Java 对象结构]，其实就是 JVM 层面的 instanceOopDesc 实例的 C++ 数据结构，如下所示：
 * class oopDesc {                             # Object 基础类型
 * private:
 * volatile markOop _mark;        # 对象头
 * union _metadata {
 * wideKlassOop _klass;           # 类指针
 * narrowOop _compressed_klass;   # 压缩类指针
 * } _metatdata;
 * ... ...
 * }
 * class instanceOopDesc : public oopDesc {}  # Object 类型
 * class arrayOopDesc : public oopDesc {}     # Array 类型
 * class InstanceKlass : public Kclass {}     # Class 类型
 * class Klass : public Metadata {
 * OopHandle _java_mirror;                # “Java 镜像”，指向 instanceOopDesc 实例。
 * }
 * 总体来说，Java 对象结构由三个部分组成：对象头、成员变量和（8 倍数）对齐填充字节。
 * 其中对象头包括三个字段：Mark Word（标记字）、类实例指针和数组长度。
 * - 如果对象是数组类型，对象头中需要记录数组长度。因为 JVM 虚拟机可以通过 Java 对象元数据信息确定 Java 对象的大小，但无法从数组的元数据信息来确定数组的大小，所以这里需要用一块内存空间记录数组长度。
 * [Java 内置锁] 的 4 种状态就是通过 Java 对象头中的 Mark Word（标记字）进行控制的。
 * Mark Word（标记字）字段主要用来存储对象自身的运行时数据，包括哈希码、GC 分代年龄、锁状态标识、锁偏向线程 ID、偏向时间戳、轻量级锁记录指针、重量级锁指针等。
 * 下面以 32 位的 HotSpot VM 为例，给出不同锁状态下的 Mark Word（标记字）的字段结构。
 * 【内置锁状态】          【25】                            【4（GC 分代年龄）】          【1（偏向锁标识）】     【2（锁状态）】
 * |无锁状态|       |对象的 HashCode（25）|                     |GC 分代年龄|                  |0|                 |01|
 * |偏向锁状态|     |偏向线程 ID（23） + 偏向时间戳（2）|          |GC 分代年龄|                  |1|                 |01|
 * |轻量级锁状态|   |抢（轻量级）锁线程方法栈帧中的锁记录（无锁 Mark Word（标记字）缓存 + 锁对象指针）指针（30）|              |00|
 * |重量级锁状态|                  |重量级锁（对象内部的 monitor 监视器）指针（30）|                                   |10|
 * |GC 标记|                                  |NULL（30）|                                                     |11|
 * --------------------------------------------------------------------------------------------------------------------------------
 * [内置锁] 状态的变化场景：
 * -（有 HashCode）无锁状态 -> 轻量级锁状态 -> 重量级锁状态。
 * -（无 HashCode）无锁状态 -> 偏向锁状态 -> 计算 HashCode，[撤销偏向锁]，存储 HashCode  -> 重量级锁状态；
 * -（无 HashCode）无锁状态 -> 偏向锁状态 -> [撤销偏向锁]，进入轻量级锁状态 -> 重量级锁状态。
 * 下面以最后一种场景，给出以上 4 中 [内置锁] 状态的执行过程：
 * 1）线程执行 synchronized 临界区代码时，JVM 首先检查 Java 对象头中的 Mark Word（标记字）的偏向锁标识是否为 0，且锁状态是否为 01，如果都满足，确认 [内置锁] 是无锁状态，直接将 Mark Word（标记字）的偏向锁标识设置为 1，并设置自身线程 ID 为偏向线程 ID，设置偏向时间戳，执行临界区代码；
 * 2）线程抢锁时，JVM 首先检查 Mark Word（标记字）的偏向锁标识是否设置为 1，且状态是否为 01？如果都满足，说明 [内置锁] 为偏向锁状态，然后检查 Mark Word（标记字）中的偏向线程 ID 是否为抢锁线程 ID？如果是，抢锁线程直接执行临界区代码；否则，抢锁线程通过 CAS 操作直接更新 Mark Word（标记字）中的偏向线程 ID 为抢锁线程 ID，如果成功，抢锁线程执行临界区代码，[内置锁] 仍处于偏向锁状态；
 * 3）如果 2）失败，说明 [内置锁] 发生了竞争，撤销偏向锁，更新 Mark Word（标记字）的锁状态为 00，升级为轻量级锁状态；
 * 4）线程抢锁时，基于 3），抢锁线程会在方法栈帧中创建一条锁记录，由 Mark Word（标记字）缓存 + 锁对象指针组成，然后 JVM 使用 CAS 将 Mark Word（标记字）中的锁记录指针更新为抢锁线程的锁记录指针。如果成功，抢锁线程成功获得锁，执行临界区代码；如果失败，JVM 使用 CAS 自旋（10 次左右）不断进行尝试；如果 CAS 自旋成功，抢锁线程成功获得锁，执行临界区代码，[内置锁] 仍处于轻量级锁状态；
 * 5）如果 4）CAS 自旋失败，轻量级锁膨胀为重量级锁，抢锁线程进入阻塞状态，等待被唤醒。
 * 总体来说，线程 A 在执行 synchronized 临界区代码时，如果不存在锁的争用，[内置锁] 默认为偏向锁状态；一旦出现线程 B 竞争 [内置锁]，就会升级为轻量级锁状态；如果 [内置锁] 竞争很激烈，轻量级锁状态下的 CAS 自旋操作就很容易达到阈值，进而升级为重量级锁状态，阻塞抢锁线程。
 * 5. synchronized 关键字的优点：
 * 1）不需要显式抢占和释放锁，由 JVM 底层完成，而且任何一个 Java 对象都能作为一个 [内置锁]，使用方便；
 * 2）在锁竞争不激烈的情况下，通过 CAS 自旋机制可以减少重量级锁产生的性能消耗，效率很高。
 * 6. synchronized 关键字的缺点：
 * 1）因为 Java 对象内置锁是由 JVM 底层自动完成的，应用程序无法主动释放锁，而且抢锁过程是永久阻塞式、不可中断式的，灵活性太差。
 * 2）功能太单一，不具备一些比较高级的功能，例如：非阻塞抢锁、限时抢锁、可中断抢锁、维护多个条件队列，以提高锁的使用效率等。
 * 3）在锁竞争激烈的情况下，Java 内置锁会膨胀为重量级锁，该锁是基于 Linux 内核中的 Mutex Lock 互斥锁实现的，抢锁线程的阻塞与唤醒需要 CPU 在用户态和核心态之间频繁切换，导致其性能急剧下降。
 * Lock 显式锁就是为了解决 Java 内置锁的释放问题、功能单一问题以及性能问题而产生的高级锁。
 * 面试题：
 * - Java 中提供了 synchronized 关键字，为什么还要提供 Lock 显式锁呢？
 * - 什么是死锁？为什么使用 synchronized 关键字可能会产生死锁问题？死锁产生的条件是什么？如何避免死锁？
 * 死锁指的是两个或两个以上的线程在执行过程因争抢（锁）资源而造成的一种相互等待的现象。
 * 若无外力作用，它们将陷入僵局，均无法继续向下执行。
 * 死锁的发生必须满足四个必要条件：独占、不可剥夺、请求与保持、循环等待。
 * 使用 synchronized 关键字是不需要显式抢占和释放锁，由 JVM 底层完成，也就导致应用程序无法主动释放锁，而且它是永久阻塞式、不可中断式的进行抢锁，满足了死锁产生的四个必要条件，很有可能产生死锁问题。
 * 避免死锁发生的方法：破坏死锁产生的四个必要条件。
 * 例如：
 * - 将系统中所有的（锁）资源设置标志位并排序，规定所有申请（锁）资源的线程必须按照一定的顺序（升序或降序）进行操作，从而阻止循环等待条件，避免死锁；
 * - 非阻塞抢锁、限时阻塞抢锁、可中断抢锁，以及给锁加一个过期时间，到期自动释放锁等，破坏请求与保持、不可剥夺、循环等待等条件，避免死锁，例如如果使用的是 JUC 显式锁的 lockInterruptibly() 方法可中断抢锁，就可以在检测到死锁发生后，使用 Thread.interrupt() 方法去中断死锁线程，不让死锁线程一直等待下去。
 * 死锁检测方法：
 * - 如果使用的是 synchronized 内置锁，当死锁发生时，JVM 会在生成线程方法堆栈转储快照时输出锁定信息，标识死锁或其他异常行为的来源。
 * - 如果使用的是 JUC 显式锁，则可以使用 ManagementFactory 获取 ThreadMXBean 线程管理实例，然后通过以下方法输出死锁或其他异常行为的来源。
 * - ManagementFactory.getThreadMXBean.findDeadlockedThreads()，检测由于抢占 Java 内置锁，JUC 显式锁所引起死锁的线程。
 * - ManagementFactory.getThreadMXBean.findMonitorDeadlockedThreads()，仅检测由于抢占 Java 内置锁所引起死锁的线程。
 */